import java.util.Collection;
import java.util.HashSet;

public abstract class Action {
  public abstract void perform(Battle battle);
  public abstract Collection<Outcome> branch_out(Battle battle);
  public abstract double calc_value(Battle orig_battle);

  //struct
  public static class Outcome {
    public Outcome(Battle battle, double probability)
      {this.battle = battle;   this.probability = probability;}
    public Battle battle;
    public double probability;
    public void testPrint() {
      System.out.println("OUTCOME:");
      System.out.println("Probability = " + probability);
    }
  }

  //static "constructor" methods
  public static Action attack(final Combatant mover, final MapLocation dest, final Combatant target) {
    return new Action() {
      public void perform(Battle battle) {
        if(!valid_move(battle,mover,dest))
          throw new Error("Can't make that move.");
        Combatant real_mover = battle.at(battle.loc(mover)); //hack
        Combatant real_target = battle.at(battle.loc(target)); //hack
        battle.move(real_mover,dest);
        battle.attack(real_mover,real_target);
        battle.mark_gray(real_mover);
      }
      public Collection<Outcome> branch_out(Battle battle) {
        if(!valid_move(battle,mover,dest))
          throw new Error("Can't make that move.");
        Outcome after_move = new Outcome(battle.clone(),1.0);
        after_move.battle.move(mover,dest);
        Combatant real_mover = after_move.battle.at(after_move.battle.loc(mover)); //hack
        Combatant real_target = after_move.battle.at(after_move.battle.loc(target)); //hack
        if(real_mover == null || real_target == null)
          throw new Error("Assertion failed! "+this);
        Combatant.CombatInfo mover_info = new Combatant.CombatInfo(after_move.battle,real_mover,real_target);
        Combatant.CombatInfo target_info = new Combatant.CombatInfo(after_move.battle,real_target,real_mover);

//        System.out.println("Mover Loc: " + after_move.battle.loc(real_mover));
//        System.out.println("Target Loc: " + after_move.battle.loc(real_target));
//        System.out.println("Combat Info: " + mover_info); //debug

        Collection<Outcome> result = new HashSet<Outcome>();

        single_attack(after_move,mover,target,mover_info,result);

        if(target_info.multiplicity() > 0) {
          Collection<Outcome> intermediate_collection = new HashSet<Outcome>();
          intermediate_collection.addAll(result);
          for(Outcome outcome: intermediate_collection) {
            if(outcome.battle.loc(target) != null
                && outcome.battle.loc(mover) != null) { //is still alive //hack
              result.remove(outcome);
              single_attack(outcome,target,mover,target_info,result);
            }
          }
        }

        Combatant faster;
        Combatant slower;
        Combatant.CombatInfo faster_info;
        if(mover_info.multiplicity() == 2) {
          faster = mover;   slower = target;   faster_info = mover_info;
        } else if(target_info.multiplicity() == 2) {
          faster = target;   slower = mover;   faster_info = target_info;
        } else {
          faster = null;   slower = null;   faster_info = null;
        }

        if(faster != null) {
          Collection<Outcome> intermediate_collection = new HashSet<Outcome>();
          intermediate_collection.addAll(result);
          for(Outcome outcome: intermediate_collection) {
            if(outcome.battle.loc(slower) != null
                && outcome.battle.loc(faster) != null) { //is still alive //hack
              result.remove(outcome);
              single_attack(outcome,faster,slower,faster_info,result);
            }
          }
        }

        return result;
      }
      public double calc_value(Battle orig_battle) {
    	  return value_func(branch_out(orig_battle),dest,orig_battle.loc(target));
      }
      public String toString() {
        return mover.cclass()+" attacking "+target.name()+" from "+dest;
      }
    };
  }
  public static Action staff(final Combatant mover, final MapLocation dest, final Combatant target) {
    return new Action() {
    	public double calc_value(Battle orig_battle) {
    		return 0.0; //stub
    	}
      public void perform(Battle battle) {
        if(!valid_move(battle,mover,dest))
          throw new Error("Can't make that move.");
        Combatant real_mover = battle.at(battle.loc(mover)); //hack
        Combatant real_target = battle.at(battle.loc(target)); //hack
        battle.move(real_mover,dest);
        real_mover.staff().use(battle,real_mover,real_target);
        battle.mark_gray(real_mover);
      }
      public Collection<Outcome> branch_out(Battle b) {
        Collection<Outcome> result = new HashSet<Outcome>();
        Battle after_staff = b.clone();
        perform(after_staff);
        result.add(new Outcome(after_staff,1.0));
        return result;
      }
      public String toString() {
        return mover.cclass()+" healing "+target.cclass()+" from "+dest;
      }
    };
  }
  public static Action wait(final Combatant mover, final MapLocation dest) {
    return new Action() {
    	public double calc_value(Battle orig_battle) {
    		return 0.0; //stub
    	}
      public void perform(Battle battle) {
        if(!valid_move(battle,mover,dest))
          throw new Error("Can't make that move.");
        Combatant real_mover = battle.at(battle.loc(mover)); //hack
        battle.move(real_mover,dest);
        battle.mark_gray(real_mover);
      }
      public Collection<Outcome> branch_out(Battle b) {
        Collection<Outcome> result = new HashSet<Outcome>();
        Battle after_staff = b.clone();
        perform(after_staff);
        result.add(new Outcome(after_staff,1.0));
        return result;
      }
      public String toString() {
        return mover.cclass()+" moving to "+dest;
      }
    };
  }

  //disallow direct construction of this class; use public static methods above
  private Action() {}

  private static boolean valid_move(Battle context, Combatant mover, MapLocation dest) {
    return context.hasnt_moved(mover)
        && (context.at(dest) == null || context.at(dest).equals(mover))
        && context.move_range(mover).containsKey(dest);
  }

  private static void single_attack(Outcome outcome, Combatant a, Combatant d, Combatant.CombatInfo i, Collection<Outcome> result) {
    Battle b = outcome.battle;
    if(i.hit() < 100) {
      double miss_probability = outcome.probability * (1.0 - i.hit()/100.0);
      Battle miss = b.clone();
      Combatant miss_a = miss.at(miss.loc(a)); //hack
      Combatant miss_d = miss.at(miss.loc(d)); //hack
      miss_a.attack_miss(miss,miss_d);
      miss.mark_gray(miss_a);
      result.add(new Outcome(miss,miss_probability));
    }
    if(i.hit() > 0) {
      double hit_probability = outcome.probability * i.hit()/100.0;
      Battle hit = b.clone();
      Combatant hit_a = hit.at(hit.loc(a)); //hack
      Combatant hit_d = hit.at(hit.loc(d)); //hack
      hit_a.attack_hit(hit,hit_d);
      hit.mark_gray(hit_a);
      result.add(new Outcome(hit,hit_probability));
      if(i.critical() > 0) {
        double crit_probability = hit_probability * i.critical()/100.0;
        Battle crit = b.clone();
        Combatant crit_a = crit.at(crit.loc(a)); //hack
        Combatant crit_d = crit.at(crit.loc(d)); //hack
        crit_a.attack_critical(crit,crit_d);
        crit.mark_gray(crit_a);
        result.add(new Outcome(crit,crit_probability));
      }
    }
  }
  
  public static double value_func(Collection<Outcome> branches, MapLocation moverLoc, MapLocation targetLoc) {
	  double MAX_CHANCE_OF_DEATH = 0.25;
	  double KILL_DAMAGE = 50.0;
	  double chance_of_death = 0;
	  double avg_damage_dealt = 0;
	  double avg_damage_taken = 0;
	  System.out.println("");
	  System.out.println("STARTING VALUE CALC");
	  for (Outcome o : branches) {
		  System.out.println("");
		  System.out.println("Considering new outcome...");
		  if (moverLoc == null) {
			  chance_of_death += o.probability;
			  System.out.println("Null pointer! WE was dead when we got here.");
			  System.out.println("Adding "+o.probability+" to chance of death!");
		  }
	      else if (o.battle.at(moverLoc) == null) {
	    	  chance_of_death += o.probability;
	    	  System.out.println("We actually looked and WE dead...");
	    	  System.out.println("Adding "+o.probability+" to chance of death!");
	      }
		  else {
			  if (targetLoc == null)  {
				  System.out.println("Null pointer! THEY was dead when we got here.");
				  System.out.println("Adding "+ (KILL_DAMAGE * o.probability) + " to avg damage dealt!");
				  avg_damage_dealt += KILL_DAMAGE * o.probability; // HACK; must find better way
			  }
			  else if (o.battle.at(targetLoc) == null) {
				  avg_damage_dealt += KILL_DAMAGE * o.probability; // HACK; must find better way
				  System.out.println("We actually looked and THEY dead...");
				  System.out.println("Adding "+ (KILL_DAMAGE * o.probability) + " to avg damage dealt!");
			  }
			  else {
				  double newProb = (double)(o.battle.at(targetLoc).max_hp() - o.battle.at(targetLoc).hp()) * o.probability;
				  avg_damage_dealt += newProb;
				  System.out.println("Adding "+newProb+" to avg damage DEALT!");
			  }
			  double newProb = (double)(o.battle.at(moverLoc).max_hp() - o.battle.at(moverLoc).hp()) * o.probability;
			  avg_damage_taken += newProb;
			  System.out.println("Adding "+newProb+" to avg damage TAKEN!");
		  }
	  }
	  
	  if (chance_of_death > MAX_CHANCE_OF_DEATH) {
		  System.out.println("CONCLUSION: You'll likely die, don't even bother.");
		  return -1000.0;
	  }
	  else {
		  System.out.println("CONCLUSION: avg dealt = "+avg_damage_dealt+", avg taken = "+avg_damage_taken);
		  return (avg_damage_dealt * avg_damage_dealt) - (avg_damage_taken * avg_damage_taken);
	  }
  }
}
